查看原文
其他

Uber的技术架构之基础篇

2016-07-21 Lucie Lozinski 云头条

Uber的使命是,让出行与自来水一样可靠,无处不在,供所有人使用。为了让这成为可能,我们构建并处理复杂数据。然后,我们巧妙地把它们组合成为一个平台,让广大司机能获得业务,让乘客能抵达目的地。


屏幕截图显示了2016年春季纽约、中国和印度三地的Uber打车应用


虽然我们希望Uber的用户界面简单,但我们在背后设计了复杂的支撑系统,处理棘手的交互,支持海量的流量。我们将原来的整体式架构分成了许多部分,以便伴随业务成长而扩展。由于成百上千的微服务相互依赖,绘制一张图来表明目前Uber是如何工作的显得异常复杂,这一切在迅速变化。本文介绍了我们从2016年春天开始使用的架构。


Uber工程部门的挑战:没有免费用户,高速增长


与一些最成功的软件公司一样,我们同样遇到了全球规模问题,但是1)我们成立才短短六年,所以我们还没有解决这些问题;2)我们的业务基于现实世界,而且具有实时性。


2015年4月,Uber运营有业务的300个城市分布在地图上


不像免费增值服务,Uber只有交易型用户:乘客、司机,还有现在的食客和快递员。人们依赖我们的技术来赚钱,前往想去的地方,所以没有时间暂停下来。我们格外注重可用性和可扩展性。


就在我们扩大覆盖范围的同时,我们的服务必须能够扩展。我们架构的灵活性鼓励竞争,那样最好的想法才能胜出。这些想法未必很独特。如果有一种强大的工具,我们使用它,直到它无力满足我们的需求。当我们提出更高的要求后,我们构建内部解决方案。在过去的一年,Uber工程部门以巨大的适应性、创造力和方法准则,应对业务迅猛发展的势头。纵观2016年,我们拥有还要宏大的计划。等你读到本文,想必发生了很大的变化,不过本文概述了我们现在使用的系统。通过本文介绍,我们希望展示在使用工具和技术方面的理念。


Uber的技术架构


想象一棵树,而不是种种限制的塔式体系。看一下整个Uber使用的技术,你会看到共同的架构(好比树干),每个团队或工程办公室有不同的侧重点(好比树枝)。它完全是用同一种东西做成的,但工具和服务在各个领域大放异彩。



我们先从底层开始说起。


底层:平台


这第一篇文章关注Uber平台,这意味着支持更广泛的Uber工程部门的所有系统。平台团队创建和维护的系统让其他工程师能够构建用户使用的软件、功能和应用程序。


基础设施和存储


我们的业务在混合云模式上运行,结合使用多家云服务提供商和多个活动数据中心。如果一个数据中心出现故障,行程(以及与行程有关的所有服务)就会自动切换到另一个数据中心。我们为城市指定了地理位置最近的数据中心,但每个城市都由另一个地方的不同数据中心提供后备机制。这意味着我们的所有数据中心始终在处理行程;我们并没有 “备份”数据中心这个概念。为了提供这套基础设施,我们结合使用了内部工具和Terraform(https://www.terraform.io)。


我们的存储需求因业务发展而发生了变化。单单一个Postgres实例让我们度过了发展初期,不过随着业务迅猛发展,我们需要增加可用的磁盘存储空间,并缩短系统响应时间。



2014年夏末,Mezzanine项目(https://eng.uber.com/mezzanine-migration/)重构了系统,以匹配这个高级架构。


我们目前使用Schemaless(在MySQL上运行的内部构建系统)、Riak和Cassandra。随着时间的推移,Schemaless实例取代了单独的MySQL实例和Postgres实例,Cassandra取代了Riak,以提升速度和性能。Schemaless用于数据的长期存储;Riak和Cassandra满足高可用性低延迟方面的需求。至于复杂数据的分布式存储和分析,我们使用了Hadoop仓库。除了这些数据库外,我们在西雅图的工程师专注于构建一个新的实时数据平台。


我们使用Redis用于缓存和队列。Twemproxy通过其一致的散列算法,提供了缓存层的可扩展性,又不牺牲缓存命中率。Celery worker进程使用那些Redis实例来处理异步工作流操作。


日志


我们的服务彼此交互,还与移动设备进行交互,而那些交互对业务状况(比如动态定价)和内部使用(比如调试)来说都很重要。就日志而言,我们使用了多个Kafka集群,数据被归档到Hadoop及/或文件存储Web服务中,然后将数据从Kafka弃用。这些数据还被各个服务实时获取,并索引到ELK堆栈,用于搜索和可视化(ELK代表Elasticsearch、Logstash和Kibana)。


应用程序配置


我们使用Mesos上的Docker容器,借助一致的配置来运行微服务,具有可扩展性,并借助Aurora来处理长时间运行的服务和计划任务。我们的其中一个基础设施团队:Application Platform构建了一个模板库,把服务做入到可交付的Docker镜像中。


路由和服务发现


我们的面向服务架构(SOA)使得服务发现和路由对Uber的成功而言至关重要。在我们的复杂网络中,服务必须能够彼此联系。我们结合使用HAProxy和Hyperbahn来解决这个问题。Hyperbahn是Uber开发的一系列开源软件的一部分:Ringpop、TChannel和Hyperbahn都肩负一个共同的使命,为服务网络增添自动化、智能和性能。


遗留服务使用本地HAProxy实例,通过HTTP请求将JSON路由到其他服务,而前端Web服务器NGINX为后端服务器充当代理。有了这种可靠成熟的数据传输方式,故障排除起来就很容易,这在去年几次迁移到刚开发的系统的过程中显得至关重要。


然而,我们更注重长期可靠性而不是可调试性。替代HTTP的非传统协议(如SPDY、HTTP/2和TChannel)以及像Thrift和Protobuf这些接口定义语言将有助于从速度和可靠性方面改进我们的系统。Ringpop是一致的散列层,它为应用程序层面带来了合作和自愈合。 Hyperbahn让服务能够简单而可靠地找到其他服务,并与之联系,即便服务是由Mesos动态调度的。


不是采用过时的轮询方式来查看是否发生了变化,我们改用一种发布-订阅模式(向订户发布更新内容)。HTTP/2和SPDY更容易支持这种推送模式。改用推送模式后,Uber应用程序中几项基于轮询的功能会出现速度大幅提升。


开发和部署


Phabricator支持大量的内部操作,从代码审查、文档编制到过程自动化,不一而足。我们使用OpenGrok这种源代码搜索和相互参照引擎来搜寻代码。至于Uber的开源项目,我们使用GitHub从事开源开发,用于问题跟踪和代码审查。


Uber工程部门竭力让开发环境尽可能酷似生产环境,于是我们主要在云提供商或开发人员的笔记本电脑上运行的虚拟机上从事开发。我们构建了自己的内部部署系统来管理代码构建。Jenkins负责持续集成工作。我们结合了Packer、Vagrant、Boto和Unison,开发用于在虚拟机上构建、管理和开发的工具。我们在开发过程中使用Clusto用于库存管理。Puppet负责管理系统配置。


我们不断努力构建和维护稳定的沟通渠道,不仅仅为了我们的服务,还为了我们的工程师。至于信息发现,我们构建了uBlame(向git-blame致意),跟踪哪些团队拥有某一项服务,并构建了Whober用于查找姓名、面部、联系信息和组织结构。我们使用一个内部的文档编制网站,使用Sphinx,自动从软件库来构建文档。一项企业提醒服务提醒我们随叫随到的工程师,确保系统正常运行。大多数开发人员在其笔记本电脑上运行OS X,我们的大多数生产实例通过运行Debian Jessie来运行Linux。


语言


在较低层面,Uber的工程师主要用Python、Node.js、Go和Java来编写程序。我们最开始使用两种主要的语言:Node.js供市场团队使用,Python供其他所有人使用。如今这些第一语言仍用于在Uber运行的大多数服务。


由于高性能的原因,我们采用了Go和Java。我们为这些语言提供了一流的支持。Java充分利用了开源生态系统,并与外部技术整合起来,比如Hadoop及其他分析工具。Go为我们提供了效率、简单性和运行速度。


我们将原来的代码库分解成微服务时,丢弃并更换了旧的Python代码。异步编程模型为我们提供了更好的吞吐量。我们使用Tornado和Python,但Go直接支持并发的功能非常适合大多数新的注重性能的服务。


必要时,我们用C和C ++来编写工具(比如在系统层面开发高效率、快速度代码)。我们使用由那些语言编写的软件,比如HAProxy,但在大多数情况下,我们在实际工作中不用这些语言。


当然了,那些在架构顶层工作的系统是用Java、Go、Python和Node之外的语言编写的。


测试


为了确保我们的服务能够满足生产环境的需求,我们开发了两款内部工具:Hailstorm和uDestroy。Hailstorm驱动集成测试,并在非高峰时段模拟峰值负荷,而uDestroy有意起到了破坏作用,那样我们就能更擅长处理意外故障。


我们的员工使用应用程序的测试版,在发送到用户之前不断测试开发的新品。我们做了一个应用程序反馈报告工具,在部署到用户之前揪出任何代码错误。每当我们在Uber应用程序中拍取屏幕截图,这项功能就会提示我们在Phabricator中提交错误修复任务。


可靠性


编写后端服务的工程师负责服务运营。如果他们编写的一段代码在生产环境中出现故障,就会得到提醒。我们使用Nagios警报机制来进行监控,与一套警报系统结合起来,用于通知。


力求获得最佳可用性和每天处理10亿次打车服务,网站可靠性工程师专注于获得成功所需的服务。


2016年2月的一场技术讨论会介绍了Uber网站可靠性工程的历史。


可观察性


可观察性意味着确保Uber整体以及不同部分都顺利运行。一套系统主要由我们的纽约办事处开发,它们为Uber工程部门充当遍布全球各地的眼睛、耳朵和免疫系统。


遥测


我们用Go开发了M3,收集并存储来自Uber工程部门每一个部分(每台服务器、主机服务和每段代码)的度量指标。


我们收集数据后寻找趋势。我们通过修改Grafana(https://github.com/grafana/grafana)来构建仪表板和图形,以便更直观地将信息置于上下文来研究。每个查看仪表板的工程师往往关注某个集团或地区的数据、一组试验方面的数据,或者是与某个产品有关的数据。我们为Grafana添加了数据交叉分析功能。


异常检测


Argos是我们的内部异常检测工具,负责分析进来的度量指标,并基于历史数据,将它们与预测模型进行比对,从而确定当前数据是不是在预期范围内。



我们将通过异常检测算法每小时生成的动态阈值(红色和黄色)与进入的数据流进行比对。我们添加了实际的每一分钟的数据,以作参考(蓝色),这当然是事先未知的。阈值密切遵循实际度量指标的模式。


针对度量指标采取行动


Uber的μMonitor工具让工程师能够查看这些信息和阈值(无论是静态信息,还是Argos的智能阈值),并采取相应行动。如果数据流超出界限――比如说出行减少到某个城市的某个阈值以下,该信息就会传送到通用操作网关(Common Action Gateway)。这是我们的自动响应系统。不是说有问题就通知工程师,它会采取针对性措施,缩短问题的持续时间。如果部署带来了某个问题,就会自动回滚。


我们的观测工具大多数保留在Uber里面,因为它们是专门针对我们的基础设施,不过我们希望很快能够选取并开源通用组件。


创造性地使用数据


Storm和Spark将数据流处理成有用的业务度量指标。我们的数据可视化团队创建可重复使用的框架和应用程序,以使用可视化数据。


表和置信区间可视化为我们的A / B测试平台Morpheus添加了功能


地图和试验团队依赖数据可视化,将数据转换为清楚、合理的信息。城市运营团队可以在地图上查看所在城市中实时移动的司机,而不是靠枯燥乏味的SQL查询获取洞察力。


我们使用JavaScript(ES5和ES6)以及React来构建数据产品,作为我们的核心工具。我们还在可视化组件中使用所有的图形Web标准:SVG、Canvas 2D和WebGL。我们开发的许多库是开源库,比如react-map-gl,我们依赖它们用于地图可视化:



可视化展示了react-map-gl的功能,这是由Uber数据可视化团队为MapboxGL-JS开发的一个包装器。


我们还开发了可视化框架,那样R、Shiny和Python等其他技术可以使用这些框架用于我们的图表组件。我们想要高数据密度的可视化,可以在浏览器中顺畅地执行。为了同时实现这两个目标,我们开发了基于WebGL的开源可视化工具。



这张Uber热图显示了载客车辆的密度。然后,我们从百分位分布上去除了高百分位(top percentile),看看基本概况。


地图


Uber的地图团队格外重视数据集、算法和地图数据、显示和路由等方面的工具,以及用于收集和推荐地址及位置的系统。地图服务(Map Service)在主要基于Java的堆栈上运行。


这方面容量最高的服务是Gurafu(https://eng.uber.com/engineering-an-efficient-route/),它提供了一组实用工具,可用于处理道路地图数据,通过提供更复杂的路由选项,提高效率和精度。Gurafu的前端是µETA,它在原始ETA(比如试验群组分段等)上增添了业务逻辑层。Gurafu和µETA都是建立在DropWizard框架基础上的Web服务。


我们的业务和客户依赖高度精确的ETA,所以地图服务工程师花费了大量和时间,提高这些系统的准确性。我们执行了ETA误差分析,以识别和修复故障来源。除了准确性外,问题的规模值得关注:每一秒,整个公司的系统都使用ETA信息来做出数量众多的决策。由于那些请求的延迟时间必须在5毫秒左右,算法效率就成了个大问题。我们不得不关注分配内存、执行并行计算,以及请求慢速资源(比如系统磁盘或数据中心网络)的方式。


地图服务还支持乘客和司机应用程序中的搜索框后面的所有后端技术。这些技术包括自动填写地址的搜索引擎、预测引擎和反向地理编码服务。自动填写地址的搜索引擎可以对地方和地址进行高速、偏向本地的偏置位置搜索。我们的预测引擎使用机器学习,基于结合的用户历史及其他信号,预测乘客目的地。预测占了输入目的地的50%左右。反向地理编码根据GPS来确定用户位置,另外我们根据总体出行历史记录,为建议的Uber载客地点提供了额外信息。


虽然Uber的技术和挑战可能会变,但我们的使命和克服挑战的文化将持续不变。


云头条编译|未经授权谢绝转载


欢迎加入交流,群主微信:aclood



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存